#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

#include "org_jfuse_Fuse.h"
#include <fuse.h>


////////////////////////////////////////////////////////////////////////////////
// globals

// global handle to our virtual machine. initialized by JNI_OnLoad
JavaVM* g_vm = NULL;



////////////////////////////////////////////////////////////////////////////////
// tools

// used for returning errors
class xerrno
{
private:
	int _errno;
public:
	xerrno(int arg)
	{
		_errno = arg;
	}
	int get_errno()
	{
		return _errno;
	}
};

// fail with an error. log the error and return ENOSYS.
void xfail(const char* fmt, ...)
{
	char buf[8192];
	va_list va;
	va_start(va, fmt);
	vsnprintf(buf, sizeof(buf), fmt, va);
	va_end(va);
	fprintf(stderr, "error: %s\n", buf);
	throw xerrno(ENOSYS);
}

// wrapper for automatic thread-attaching / detaching to the java vm
class xenv
{
private:
	JNIEnv* _env;
public:
	xenv()
	{
		jint res = g_vm->GetEnv((void**)&_env, JNI_VERSION_1_6);
		if (res == JNI_EDETACHED) {
			res = g_vm->AttachCurrentThread((void**)&_env, NULL);
		}
		if (res != 0 || _env == NULL) xfail("VM init failed: res=%d _env=%p\n", res, _env);
	}
	~xenv()
	{
		g_vm->DetachCurrentThread();
	}
	JNIEnv* operator->()
	{
		return _env;
	}
};

////////////////////////////////////////////////////////////////////////////////
// fuse operation handlers

int jfuse_getattr(const char *path, struct stat *stbuf);
int jfuse_readlink(const char* path, char* buffer, size_t buffer_len);
int jfuse_mkdir(const char* path, mode_t mode);
int jfuse_unlink(const char* path);
int jfuse_rmdir(const char* path);
int jfuse_symlink(const char* from, const char* to);
int jfuse_rename(const char* from, const char* to);
int jfuse_link(const char* from, const char* to);
int jfuse_chmod(const char* path, mode_t mode);
int jfuse_chown(const char* path, uid_t uid, gid_t gid);
int jfuse_truncate(const char* path, off_t offset);
int jfuse_utime(const char* path, struct utimbuf* ut);
int jfuse_open(const char *path, struct fuse_file_info *fi);
int jfuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi);
int jfuse_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi);
int jfuse_statfs(const char* path, struct statvfs *svfs);
int jfuse_opendir(const char* path, struct fuse_file_info *fi);
int jfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi);
void *jfuse_init(struct fuse_conn_info *conn);
int jfuse_access(const char *path, int mode);
int jfuse_create(const char* path, mode_t mode, struct fuse_file_info *fi);
int jfuse_ftruncate(const char* path, off_t offset, struct fuse_file_info *fi);
int jfuse_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi);
int jfuse_utimens(const char *path, const struct timespec tv[2]);

int jfuse_getattr(const char *path, struct stat *stbuf)
{
	return jfuse_fgetattr(path, stbuf, NULL);
}

int jfuse_readlink(const char* path, char* buffer, size_t buffer_len)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_readlink", "(Ljava/lang/String;)Ljava/lang/String;");
		if (!mid) xfail("cannot find method");

		jstring res = (jstring)env->CallStaticObjectMethod(clazz, mid, jpath);
		
		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		if (!res) xfail("null return from class");

		const char* res_b = env->GetStringUTFChars(res, NULL);
		strncpy(buffer, res_b, buffer_len);
		env->ReleaseStringUTFChars(res, res_b);

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_mkdir(const char* path, mode_t mode)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_mkdir", "(Ljava/lang/String;I)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath, (jint)mode);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_unlink(const char* path)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_unlink", "(Ljava/lang/String;)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_rmdir(const char* path)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_rmdir", "(Ljava/lang/String;)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_symlink(const char* from, const char* to)
{
	try {
		xenv env;

		jstring jfrom = env->NewStringUTF(from);
		jstring jto = env->NewStringUTF(to);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_symlink", "(Ljava/lang/String;Ljava/lang/String;)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jfrom, jto);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_rename(const char* from, const char* to)
{
	try {
		xenv env;

		jstring jfrom = env->NewStringUTF(from);
		jstring jto = env->NewStringUTF(to);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_rename", "(Ljava/lang/String;Ljava/lang/String;)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jfrom, jto);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_link(const char* from, const char* to)
{
	try {
		xenv env;

		jstring jfrom = env->NewStringUTF(from);
		jstring jto = env->NewStringUTF(to);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_link", "(Ljava/lang/String;Ljava/lang/String;)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jfrom, jto);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_chmod(const char* path, mode_t mode)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_chmod", "(Ljava/lang/String;I)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath, (jint)mode);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_chown(const char* path, uid_t uid, gid_t gid)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_chown", "(Ljava/lang/String;II)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath, (jint)uid, (jint)gid);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_truncate(const char* path, off_t offset)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_truncate", "(Ljava/lang/String;J)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath, (jlong)offset);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_open(const char *path, struct fuse_file_info *fi)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_open", "(Ljava/lang/String;)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("jfuse_open: exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);
		if (size > 2147483647) xfail("size overflow"); 
		jint jsize = size;

		jbyteArray jbuf = env->NewByteArray(jsize);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_read", "(Ljava/lang/String;[BJ)I");
		if (mid == 0) xfail("cannot find method");

		jint res = env->CallStaticIntMethod(clazz, mid, jpath, jbuf, (jlong)offset);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("jfuse_read: exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		if (res < 0) xfail("result overflow");

		env->GetByteArrayRegion(jbuf, 0, jsize, (jbyte*)buf);

		return (int)res;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);
		if (size > 2147483647) xfail("size overflow");
		jint jsize = size;

		jbyteArray jbuf = env->NewByteArray(jsize);
		env->SetByteArrayRegion(jbuf, 0, jsize, (jbyte*)buf);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_write", "(Ljava/lang/String;[BJ)I");
		if (mid == 0) xfail("cannot find method");

		jint res = env->CallStaticLongMethod(clazz, mid, jpath, jbuf, (jlong)offset);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				mid = env->GetMethodID(ec, "printStackTrace", "()V");
				if (mid) env->CallVoidMethod(e, mid, NULL);
				xfail("jfuse_write: exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		if (res < 0) xfail("result overflow");

		return (int)res;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_statfs(const char* path, struct statvfs *svfs)
{
	memset(svfs, 0, sizeof(struct statvfs));
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_statfs", "(Ljava/lang/String;)Lorg/jfuse/FuseStatVFS;");
		if (!mid) xfail("cannot find method");

		jobject res = env->CallStaticObjectMethod(clazz, mid, jpath);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("jfuse_fgetattr: exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL); 
			env->ExceptionClear();
			return -error;
		}

		if (!res) xfail("null return from class");

		clazz = env->GetObjectClass(res);
		if (!clazz) xfail("getObjectClass");
		jfieldID fid;

		fid = env->GetFieldID(clazz, "bsize", "I");
		if (!fid) xfail("GetFieldID bsize");
		svfs->f_bsize = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "frsize", "I");
		if (!fid) xfail("GetFieldID frsize");
		svfs->f_frsize = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "blocks", "J");
		if (!fid) xfail("GetFieldID blocks");
		svfs->f_blocks = env->GetLongField(res, fid);

		fid = env->GetFieldID(clazz, "bfree", "J");
		if (!fid) xfail("GetFieldID bfree");
		svfs->f_bfree = env->GetLongField(res, fid);

		fid = env->GetFieldID(clazz, "bavail", "J");
		if (!fid) xfail("GetFieldID bavail");
		svfs->f_bavail = env->GetLongField(res, fid);

		fid = env->GetFieldID(clazz, "files", "J");
		if (!fid) xfail("GetFieldID files");
		svfs->f_files = env->GetLongField(res, fid);

		fid = env->GetFieldID(clazz, "ffree", "J");
		if (!fid) xfail("GetFieldID ffree");
		svfs->f_ffree = env->GetLongField(res, fid);

		fid = env->GetFieldID(clazz, "favail", "J");
		if (!fid) xfail("GetFieldID favail");
		svfs->f_favail = env->GetLongField(res, fid);

		fid = env->GetFieldID(clazz, "fsid", "I");
		if (!fid) xfail("GetFieldID fsid");
		svfs->f_fsid = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "flag", "I");
		if (!fid) xfail("GetFieldID flag");
		svfs->f_flag = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "namemax", "I");
		if (!fid) xfail("GetFieldID namemax");
		svfs->f_namemax = env->GetIntField(res, fid);
		
		
		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_opendir(const char* path, struct fuse_file_info *fi)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_opendir", "(Ljava/lang/String;)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_readdir", "(Ljava/lang/String;)[Ljava/lang/String;");
		if (mid == 0) xfail("cannot find method");
		
		jobjectArray res = (jobjectArray)env->CallStaticObjectMethod(clazz, mid, jpath);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		if (!res) xfail("null return from class");

		filler(buf, ".", NULL, 0);
		filler(buf, "..", NULL, 0);

		for (int i=0; i<env->GetArrayLength(res); i++) {
			jstring name = (jstring)env->GetObjectArrayElement(res, i);
			const char* name_b = env->GetStringUTFChars(name, NULL);
			filler(buf, name_b, NULL, 0);
			env->ReleaseStringUTFChars(name, name_b);	
		}

		return 0;	

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

void *jfuse_init(struct fuse_conn_info *conn)
{
	return NULL;
}

int jfuse_access(const char *path, int mode)
{
	return 0;
}

int jfuse_create(const char* path, mode_t mode, struct fuse_file_info *fi)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_create", "(Ljava/lang/String;I)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath, (jint)mode);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				mid = env->GetMethodID(ec, "printStackTrace", "()V");
				if (mid) env->CallVoidMethod(e, mid, NULL);
				xfail("jfuse_create: exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}		
}

int jfuse_ftruncate(const char* path, off_t offset, struct fuse_file_info *fi)
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_truncate", "(Ljava/lang/String;J)V");
		if (!mid) xfail("cannot find method");

		env->CallStaticVoidMethod(clazz, mid, jpath, (jlong)offset);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				mid = env->GetMethodID(ec, "printStackTrace", "()V");
				if (mid) env->CallVoidMethod(e, mid, NULL);
				xfail("jfuse_ftruncate: exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}		
}

int jfuse_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
{
	memset(stbuf, 0, sizeof(struct stat));
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_getattr", "(Ljava/lang/String;)Lorg/jfuse/FuseStat;");
		if (!mid) xfail("cannot find method");

		jobject res = env->CallStaticObjectMethod(clazz, mid, jpath);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				env->ExceptionDescribe();
				xfail("jfuse_fgetattr: exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL); 
			env->ExceptionClear();
			return -error;
		}

		if (!res) xfail("null return from class");

		clazz = env->GetObjectClass(res);
		if (!clazz) xfail("getObjectClass");
		jfieldID fid;

		fid = env->GetFieldID(clazz, "dev", "I");
		if (!fid) xfail("GetFieldID dev");
		stbuf->st_dev = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "ino", "I");
		if (!fid) xfail("GetFieldID ino");
		stbuf->st_ino = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "mode", "I");
		if (!fid) xfail("GetFieldID mode");
		stbuf->st_mode = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "nlink", "I");
		if (!fid) xfail("GetFieldID nlink");
		stbuf->st_nlink = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "uid", "I");
		if (!fid) xfail("GetFieldID uid");
		stbuf->st_uid = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "gid", "I");
		if (!fid) xfail("GetFieldID gid");
		stbuf->st_gid = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "rdev", "I");
		if (!fid) xfail("GetFieldID rdev");
		stbuf->st_rdev = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "size", "J");
		if (!fid) xfail("GetFieldID size");
		stbuf->st_size = env->GetLongField(res, fid);

		fid = env->GetFieldID(clazz, "blksize", "I");
		if (!fid) xfail("GetFieldID size");
		stbuf->st_blksize = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "blocks", "I");
		if (!fid) xfail("GetFieldID blocks");
		stbuf->st_blocks = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "atime", "I");
		if (!fid) xfail("GetFieldID atime");
		stbuf->st_atime = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "mtime", "I");
		if (!fid) xfail("GetFieldID mtime");
		stbuf->st_mtime = env->GetIntField(res, fid);

		fid = env->GetFieldID(clazz, "ctime", "I");
		if (!fid) xfail("GetFieldID ctime");
		stbuf->st_ctime = env->GetIntField(res, fid);

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}
}

int jfuse_utimens(const char *path, const struct timespec tv[2])
{
	try {
		xenv env;

		jstring jpath = env->NewStringUTF(path);

		jclass clazz = env->FindClass("org/jfuse/Fuse");
		if (!clazz) xfail("cannot find class");

		jmethodID mid = env->GetStaticMethodID(clazz, "fuse_utime", "(Ljava/lang/String;JJ)V");
		if (!mid) xfail("cannot find method");

		jlong actime = ((jlong)tv[0].tv_sec) * 1000 + (tv[0].tv_nsec / 1000 / 1000);
		jlong modtime = ((jlong)tv[1].tv_sec) * 1000 + (tv[1].tv_nsec / 1000 / 1000);
		env->CallStaticVoidMethod(clazz, mid, jpath, actime, modtime);

		if (env->ExceptionCheck() == JNI_TRUE) {
			jthrowable e = env->ExceptionOccurred();
			if (!e) xfail("exceptioncheck but not exception?");
			jclass ec = env->GetObjectClass(e);
			jmethodID mid = env->GetMethodID(ec, "getErrNo", "()I");
			if (!mid) {
				mid = env->GetMethodID(ec, "printStackTrace", "()V");
				if (mid) env->CallVoidMethod(e, mid, NULL);
				xfail("jfuse_utimens: exception in handler");
			}
			int error = env->CallIntMethod(e, mid, NULL);
			env->ExceptionClear();
			return -error;
		}

		return 0;

	} catch (xerrno& e) {
		return -e.get_errno();
	}		

	return -ENOTSUP;
}

// jni init function, called by JVM when library is loaded.
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*)
{
	g_vm = vm;
	return JNI_VERSION_1_6;
}

// called from java. does not return, but calls org.jfuse.Fuse.fuse_... callback methods.
// private static native int fuse_main(String[] args);
JNIEXPORT jint JNICALL Java_org_jfuse_Fuse_fuse_1main(JNIEnv* env, jclass clazz, jobjectArray args)
{
	int args_len = env->GetArrayLength(args);
	int argc = args_len + 1;
	const char** argv = new const char*[args_len + 2];
	argv[0] = "/binary";
	argv[args_len+1] = NULL;
	for (int i=0; i<args_len; i++) {
		jstring arg = (jstring)env->GetObjectArrayElement(args, i);
		const char* arg_bin = env->GetStringUTFChars(arg, NULL);
		argv[i+1] = strdup(arg_bin);
		env->ReleaseStringUTFChars(arg, arg_bin);
	}
	
	static struct fuse_operations ops = { 0 };
	ops.getattr 	= jfuse_getattr;
	ops.readlink 	= jfuse_readlink;
	// mknod
	ops.mkdir 		= jfuse_mkdir;
	ops.unlink 		= jfuse_unlink;
	ops.rmdir 		= jfuse_rmdir;
	ops.symlink 	= jfuse_symlink;
	ops.rename 		= jfuse_rename;
	ops.link 		= jfuse_link;
	ops.chmod 		= jfuse_chmod;
	ops.chown 		= jfuse_chown;
	ops.truncate 	= jfuse_truncate;
	// utime
	ops.open 		= jfuse_open;
	ops.read 		= jfuse_read;
	ops.write 		= jfuse_write;
	ops.statfs 		= jfuse_statfs;
	// flush
	// release
	// fsync
	// setxattr
	// getxattr
	// listxattr
	// removexattr
	ops.opendir 	= jfuse_opendir;
	ops.readdir 	= jfuse_readdir;
	// releasedir
	// fsyncdir
	ops.init		= jfuse_init;
	// destroy
	ops.access		= jfuse_access;
	ops.create 		= jfuse_create;
	ops.ftruncate = jfuse_ftruncate;
	ops.fgetattr 	= jfuse_fgetattr;
	// lock
	ops.utimens		= jfuse_utimens;
	// bmap
	// ioctl
	// poll
#if (__FreeBSD__ >= 10)	
	// setvolname
	// exchange
	// getxtimes
	// setbkuptime
	// setchgtime
	// setcrtime
	// chflags
	// setattr_x
	// fsetattr_x	
#endif

	int res = fuse_main(argc, (char**)argv, &ops, NULL);

	for (int i=0; i<args_len; i++) {
		delete argv[i+1];
	}
	delete argv;

	return res;
}


